package com.stu.info;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;

import com.stu.aq.*;
import com.stu.status.*;
import com.stu.usb.SerialIOManager;

public class VehicleStatusFragment extends CommandFragment implements SerialIOManager.Listener {
	private float receiverScale;
	private StatusManager statusManager;
	private String status;
	private NumberFormat statusFormat;
	private Timer timer;
	private boolean isAlive;
	private boolean stopFlag;
	private Spinner spinner;
	private Button upload, version;
	private View view;

	// indexes match order in ArrayList<Fragment>
	private class FragIndex {
		public final static int COMPASS = 0;
		public final static int JOYSTICK = 1;
		public final static int STATUS = 2;
		public final static int INDICATOR = 3;
		public final static int MOTORS = 4;
		//public final static int VERSION = 5;
	}
	private ArrayList<Fragment> fragments;

	private final static int COUNT = 24;
	private class Status {
		public double	roll, pitch, heading;	// in degrees
		public int		motors[];
		public boolean	armed, hold, mode;
		public String	altitude, battery;
		public int		rroll, rpitch, ryaw, rthrottle, rmode, raux1;
		
		public Status() {
			motors = new int[AQ.motorCount];
		}
	}
	
	public VehicleStatusFragment() {
		status = null;
		stopFlag = false;
		view = null;
		statusFormat = NumberFormat.getNumberInstance();
		statusFormat.setMaximumFractionDigits( 1 );
		statusFormat.setMinimumFractionDigits( 1 );
		receiverScale = 100.0f / (float)(AQ.receiverMax - AQ.receiverMin);

		// added in the same order as FragIndex
		fragments = new ArrayList<Fragment>();
		fragments.add( new CompassFragment() );
		fragments.add( new JoystickFragment() );
		fragments.add( new SystemStatusFragment() );
		fragments.add( new IndicatorFragment() );
		fragments.add( new MotorsStatusFragment() );
		//fragments.add( new VersionFragment() );
		
		statusManager = new StatusManager();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
	  Bundle savedInstanceState) {
		view = inflater.inflate( R.layout.view_status, container, false );
		// When this view is swapped back in we can't reuse the fragment inflaters
		//  so the fragments need to be added into frames in the view status layout at run time.
		// The order of the add is also important so dependent frames can be sized.
		FragmentTransaction ft = getFragmentManager().beginTransaction();
		ft.replace( R.id.CompassFrame, fragments.get(FragIndex.COMPASS) );
		ft.replace( R.id.JoystickFrame, fragments.get(FragIndex.JOYSTICK) );
		ft.replace( R.id.SystemStatusFrame, fragments.get(FragIndex.STATUS) );
		ft.replace( R.id.IndicatorFrame, fragments.get(FragIndex.INDICATOR) );
		ft.replace( R.id.MotorsFrame, fragments.get(FragIndex.MOTORS) );
		//ft.replace( R.id.VersionFrame, fragments.get(FragIndex.VERSION) );
		ft.replace( R.id.ConfigView, AQ.getCurrentConfig(), AQ.configInfo.get(AQ.configIndex).tag );
		ft.commit();

		spinner = (Spinner)view.findViewById(R.id.ConfigSpinner);
		// Create an ArrayAdapter using the string array and a default spinner layout
		ArrayAdapter<AQ.ConfigInfo> adapter = new ArrayAdapter<AQ.ConfigInfo>( getActivity(),
		  android.R.layout.simple_spinner_item, AQ.configInfo );
		// Specify the layout to use when the list of choices appears
		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		// Apply the adapter to the spinner
		spinner.setAdapter(adapter);
		spinner.setSelection( AQ.configIndex );
		spinner.setOnItemSelectedListener( (AQ)getActivity() );

		upload = (Button)view.findViewById( R.id.upload );
		version = (Button)view.findViewById( R.id.version );
		setButtons( false );
		return view;
	}

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		if( timer != null )
			timer.cancel();
	}

	/*
	 * 0 Motor Status 1=armed 0=disarmed
	 * 1 Kinematics X Roll (radians)
	 * 2 Kinematics Y Pitch (radians)
	 * 3 Kinematics Z Heading (radians)
	 * 4 Altitude
	 * 5 Altitude Hold 0=off 1=on 2=panic
	 * 6 Receiver Roll
	 * 7 Receiver Pitch
	 * 8 Receiver Yaw
	 * 9 Receiver Throttle
	 * 10 Receiver Mode
	 * 11 Receiver AUX1
	 * 12 Receiver AUX2
	 * 13 Receiver AUX3
	 * 14 Motor 1
	 * 15 Motor 2
	 * 16 Motor 3
	 * 17 Motor 4
	 * 18 Motor 5
	 * 19 Motor 6
	 * 20 Motor 7
	 * 21 Motor 8
	 * 22 Battery
	 * 23 Mode 0=rate 1=attitude
	 */
	@Override
	public void onNewData(byte[] data) {
if(AQ.DEBUG) Log.d("status", "onNewData");
		isAlive = true;
		if( status == null )
			status = new String( data );
		else
			status += new String( data );
		if( stopFlag ) {
if(AQ.DEBUG) Log.d("status", "onNewData stop");
			status = null;
			return;
		}
		// make sure we have the entire status by
		//  looking for a newline at the end
		int ix = status.length();
		if( status.charAt(ix-1) != '\n' )
			return;
		status = status.trim();	// remove newline and trailing spaces
		ix = status.length();
		// don't include trailing comma in substring
		if( status.charAt(ix-1) == ',' )
			ix--;
		final String values[] = status.substring(0, ix).split( "," );
		if( values.length != COUNT ) {
			status = null;
			mListener.OnCommandWarning( getId(), new AQException(VehicleStatusFragment.class.getSimpleName() +
			  ": Expected length " + String.valueOf(COUNT) + " got " + String.valueOf(values.length)) );
			return;
		}
		Status s = new Status();
		s.armed = values[0].charAt(0) == '1' ? true : false;
		s.hold = values[5].charAt(0) == '1' ? true : false;
		s.mode = values[23].charAt(0) == '1' ? false : true;
		s.altitude = values[4];
		s.battery = values[22];
		try {
			double radians = Double.parseDouble( values[1] );
			s.roll = Math.toDegrees( radians );
			radians = Double.parseDouble( values[2] );
			s.pitch = Math.toDegrees( radians );
			radians = Double.parseDouble( values[3] );
			s.heading = Math.toDegrees( radians );
			s.motors[0] = Integer.parseInt(values[14]);
			s.motors[1] = Integer.parseInt(values[15]);
			s.motors[2] = Integer.parseInt(values[16]);
			s.motors[3] = Integer.parseInt(values[17]);
			s.rroll =  (int)((Integer.parseInt(values[6]) - AQ.receiverMin) * receiverScale);
			s.rpitch =  (int)((Integer.parseInt(values[7]) - AQ.receiverMin) * receiverScale);
			s.ryaw =  (int)((Integer.parseInt(values[8]) - AQ.receiverMin) * receiverScale);
			s.rthrottle =  (int)((Integer.parseInt(values[9]) - AQ.receiverMin) * receiverScale);
			s.rmode =  (int)((Integer.parseInt(values[10]) - AQ.receiverMin) * receiverScale);
			s.raux1 =  (int)((Integer.parseInt(values[11]) - AQ.receiverMin) * receiverScale);
		} catch( NumberFormatException e ) {
			mListener.OnCommandError( getId(), new AQException(VehicleStatusFragment.class.getSimpleName() +
			  ": NumberFormatException: " + e.getMessage()) );
			status = null;
			return;
		}
		status = null;
		statusManager.handleState( new StatusData(this, s) );
	}

	@Override
	public void onRunError(Exception e) {
	}

	@Override
	public void updateUI( Object statusData ) {
		if( statusData != null ) {
			Status s = (Status)statusData;
			IndicatorFragment inf = (IndicatorFragment)fragments.get( FragIndex.INDICATOR );
			inf.setMotorStatus( s.armed );
			inf.setHoldStatus( s.hold );
			inf.setModeStatus( s.mode );
			SystemStatusFragment ssf = (SystemStatusFragment)fragments.get( FragIndex.STATUS );
			ssf.setRollValue( statusFormat.format(s.roll) );
			ssf.setPitchValue( statusFormat.format(s.pitch) );
			ssf.setHeadingValue( statusFormat.format(s.heading) );
			ssf.setAltitudeValue( s.altitude );
			ssf.setBatteryValue( s.battery );
			CompassFragment cf = (CompassFragment)fragments.get( FragIndex.COMPASS );
			cf.setCompass( (float)s.roll, (float)s.pitch, (float)s.heading );
			MotorsStatusFragment msf = (MotorsStatusFragment)fragments.get( FragIndex.MOTORS );
			msf.setValue( 0, s.motors[0] );
			msf.setValue( 1, s.motors[1] );
			msf.setValue( 2, s.motors[2] );
			msf.setValue( 3, s.motors[3] );
			JoystickFragment jf = (JoystickFragment)fragments.get( FragIndex.JOYSTICK );
			jf.setThrottle( s.rthrottle, false );
			jf.setRoll( s.rroll, false );
			jf.setYaw( s.ryaw, false );
			jf.setPitch( s.rpitch, false );
			jf.setMode(  s.rmode, false );
			jf.setAux1( s.raux1, true );
		}
		mListener.OnCommandComplete( getId(), FragmentState.UPDATING );
	}
	
	@Override
	public void update( OnCommandListener l, SerialIOManager io ) {
if(AQ.DEBUG) Log.d("status", "update");
		mListener = l;
		stopFlag = false;
		isAlive = false;
		io.setListener( this );
	    String s = "s";
	    io.writeAsync( s.getBytes() );
		// Make sure we are getting status
		timer = new Timer();
		TimerTask tt = new TimerTask() {
			@Override
			public void run() {
				checkIfAlive();
				isAlive = false;
			}
		};
		timer.scheduleAtFixedRate( tt, 1000, 1000 );
	}

	private void checkIfAlive() {
		if( !isAlive ) {
			timer.cancel();
			mListener.OnCommandError( getId(), new AQException("No flight data from AeroQuad") );
		}
	}
	
	@Override
	public void stopUpdate( OnCommandListener l, SerialIOManager io ) {
if(AQ.DEBUG) Log.d("status", "stopUpdate");
		mListener = l;
		stopFlag = true;
		timer.cancel();
		io.setListener( this );
	    String s = "x";
	    io.writeAsync( s.getBytes() );
	    // onNewData and updateUI may not be called, depending on
	    //  what still remains in the IO buffer so call the listener here
    	mListener.OnCommandComplete( getId(), FragmentState.STOPPING );
	}
	
	@Override
	public void clear() {
		((SystemStatusFragment)fragments.get(FragIndex.STATUS)).clear();
		((CompassFragment)fragments.get(FragIndex.COMPASS)).clear();
		((MotorsStatusFragment)fragments.get(FragIndex.MOTORS)).clear();
		((IndicatorFragment)fragments.get(FragIndex.INDICATOR)).clear();
		((JoystickFragment)fragments.get(FragIndex.JOYSTICK)).clear();
		setButtons( false ); 
		//((VersionFragment)fragments.get(FragIndex.VERSION)).clear();
	}
	
	public void setButtons( boolean enabled ) {
		upload.setEnabled( enabled );
		version.setEnabled( enabled );
		if( view != null )
			view.invalidate();
	}

	@Override
	public void write(OnCommandListener l, SerialIOManager io) {
	}
}
